
/**
 * @author Nicolas Franzen
 * @namespace ColorDefinitionComposition -> CODECO
 */

var CODECO = CODECO || {};

CODECO.DefaultConfiguration = {
		width : 600,
		height: 450,
		initFrom : "#0000aa", 
		initTo : "#ffff00",
		file : 'rhoneGrey.png',
		gui : true,
		addColors:true
};

CODECO.GradientMapping = function(texture, from, to){

	return {

		uniforms: {

			"tDiffuse": { type: "t", value: texture },
			"from":    { type: "c", value: from },
			"to":    { type: "c", value: to },
			"usedSteps" :  { type: "i", value: 1 },
			"positions"  : { type: "fv1", value: [ -1.0, -1.0, -1.0, -1.0, -1.0 ] },
			"colors" : { type: "v3v", value: [ new THREE.Vector3( 0.0, 0.0, 0.0 ), 
			                                       new THREE.Vector3( 0.0, 0.0, 0.0 ), 
			                                       new THREE.Vector3( 0.0, 0.0, 0.0 ), 
			                                       new THREE.Vector3( 0.0, 0.0, 0.0 ), 
			                                       new THREE.Vector3( 0.0, 0.0, 0.0 ) ] }

		},

		vertexShader: [

			"varying vec2 vUv;",

			"void main() {",

				"vUv = uv;",
				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",

			"}"

		].join("\n"),

		fragmentShader: [

			"uniform vec3 from;",
			"uniform vec3 to;",
			"uniform sampler2D tDiffuse;",
			"uniform int usedSteps;",
			"uniform float positions[ 5 ];",
			"uniform vec3 colors[ 5 ];",
			"varying vec2 vUv;",

			"void main() {",

				"vec4 texel = texture2D( tDiffuse, vUv );",
				"vec3 begin = from;",
				"float beginPos = 0.0;",
				"vec3 end = to;",
				"float endPos = 1.0;",
				"",
				"for(int i = 0; i < 5; ++i)",
				"{",
					"if(positions[i] > beginPos && positions[i] < texel.r)",
					"{",
						"",
						"beginPos = positions[i];",
						"begin = colors[i];",
						"",
					"}",
					"",
					"if(positions[i] < endPos && positions[i] > texel.r)",
					"{",
						"",
						"endPos = positions[i];",
						"end = colors[i];",
						"",
					"}",
				"}",
				"",
				"float blend = (texel.r - beginPos)/(endPos - beginPos);",
				"vec3 color = begin.rgb * (1.0 - blend) + end.rgb * blend;",

				"gl_FragColor = vec4(color, texel.w);",

			"}"

		].join("\n")

	};

};

CODECO.ColorWheelShader = function(segments, cylinderSize){

	return {
		
		uniforms: {

			"segments" :  { type: "f", value: segments },
			"cylinderSize" : { type: "f", value: cylinderSize}

		},
		vertexShader: [

		    "uniform float segments;",
			"uniform float cylinderSize;",
			"varying vec3 vPos;",

			"void main() {",

				"vPos = position / (cylinderSize * 0.5);",
				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",

			"}"

		].join("\n"),

		fragmentShader: [

		    "#define M_PI 3.1415926535897932384626433832795",             
			"uniform float segments;",
			"varying vec3 vPos;",
			"",
			"float hue2rgb(float p, float q, float t) {",
				"if(t < 0.0) t+=1.0;",
				"if(t > 1.0) t-=1.0;",
				"if(t < 1.0 / 6.0) return p + (q - p) * 6.0 * t;",
				"if(t < 1.0 / 2.0) return q;",
				"if(t < 2.0 / 3.0) return p + (q - p) * 6.0 * (2.0/3.0 - t);",
				"return p;",
			"}",
			"",
			"void main() {",

				"vec3 color;",
				"vec2 direction = normalize(vec2(vPos.x, vPos.y));",
				
				"float saturation = sqrt(vPos.x*vPos.x+vPos.y*vPos.y);",
				"float luminance = 0.5;",
				"float hue = (atan(direction.x, direction.y)+M_PI) / (2.0*M_PI);",
				
				"saturation =	floor(saturation * segments) / segments;",
				"luminance = 	floor(luminance * segments) / segments;",
				"hue =			floor(hue * segments) / segments;",
				
				"if(saturation == 0.0) {",
					"color = vec3(luminance, luminance, luminance);",
				"}",
				"else {",
					"color = vec3(0.0, 0.0, 0.0);",
					"float p = luminance <= 0.5 ? luminance * ( 1.0 + saturation ) : luminance + saturation - ( luminance * saturation );",
					"float q = ( 2.0 * luminance ) - p;",
					"color.r = hue2rgb( q, p, hue + 1.0 / 3.0 );",
					"color.g = hue2rgb( q, p, hue );",
					"color.b = hue2rgb( q, p, hue - 1.0 / 3.0 );",
				"}",
				"gl_FragColor = vec4(color, 1.0);",

			"}"

		].join("\n")

	};

};

CODECO.AppState = {
		changed : false,
		mousePos : {
			x : 0,
			y : 0
		},
		mousePressed : false,
		mouseReleased: false, 
		changer : function( )
	    {
			CODECO.AppState.changed = true;
	    },
	    mouseMove : function(e)
	    {
			CODECO.AppState.mousePos.x = e.clientX;
			CODECO.AppState.mousePos.y = e.clientY;
	    },
	    mousePress : function(){
	    	CODECO.AppState.mousePressed = true;
	    },
	    mouseRelease : function(){
	    	CODECO.AppState.mouseReleased = true;
	    }
};

CODECO.GradientElement = function(mesh, position, handler, outline) {
	this.mesh = mesh;
	this.position = position;
	this.handler  = handler;
	this.outline = outline;
};

CODECO.GradientElement.prototype = {
		updateMeshPosition : function () {
			var hsl = this.mesh.material.color.getHSL();
			this.mesh.position.x = Math.cos((hsl.h * 2.0 + 0.5) * Math.PI) * 200 * 0.5 * hsl.s;
			this.mesh.position.y = -Math.sin((hsl.h * 2.0 + 0.5) * Math.PI) * 200 * 0.5 * hsl.s;
			this.outline.position.x = Math.cos((hsl.h * 2.0 + 0.5) * Math.PI) * 200 * 0.5 * hsl.s;
			this.outline.position.y = -Math.sin((hsl.h * 2.0 + 0.5) * Math.PI) * 200 * 0.5 * hsl.s;
		},
		
		dispose : function() {
			this.mesh.material.dispose();
			this.mesh.dispose();
			this.outline.dispose();
		}
};

CODECO.GradientLab = function (canvasContainerId, configuration) {
	this.canvasContainerId = canvasContainerId;
	
	var conf = CODECO.DefaultConfiguration;
	if(configuration !== null && typeof configuration === 'object')
	{
		conf = configuration;
		for (property in CODECO.DefaultConfiguration) {
	        if(conf[property] === undefined || conf[property] === null)
	        	conf[property] = CODECO.DefaultConfiguration[property];
	    }
	}
	
	this.renderer;
	this.renderer = new THREE.WebGLRenderer();
	this.renderer.setSize( conf.width, conf.height );
	this.renderer.setClearColorHex(0xa5a5a5);
	
	document.getElementById(canvasContainerId).appendChild(this.renderer.domElement);
	
	this.scene = new THREE.Scene();
	
	this.camera = new THREE.PerspectiveCamera( 70, conf.width / conf.height, 1, 1000 );
	this.camera.position.z = 400;
	
	this.colorBegin = new THREE.Color(conf.initFrom);
	this.colorEnd = new THREE.Color(conf.initTo);
	this.addedColors = [];
	
	this.imagePlane 	= new THREE.PlaneGeometry(360, 360  * 21.0 / 32.0, 10, 10);
	this.gradientPlane  = new THREE.PlaneGeometry(50, 500, 10, 10);
	
	this.imageTexture 		= new THREE.ImageUtils.loadTexture( './textures/' + conf.file );
	this.imageTexture.anisotropy = this.renderer.getMaxAnisotropy();
	
	this.gradientTexture 	= new THREE.ImageUtils.loadTexture( './textures/gradientGrey.png' );
	this.gradientTexture.anisotropy = this.renderer.getMaxAnisotropy();
	
	this.greyGradientMaterial	 = new THREE.MeshBasicMaterial( { map: this.gradientTexture } );
	this.colorGradientMaterial	 = new THREE.ShaderMaterial(CODECO.GradientMapping(this.gradientTexture, this.colorBegin, this.colorEnd));
	
	this.greyImageMaterial		 = new THREE.MeshBasicMaterial( { map: this.imageTexture } );
	this.colorImageMaterial		 = new THREE.ShaderMaterial(CODECO.GradientMapping(this.imageTexture, this.colorBegin, this.colorEnd));
	
	this.greyGradient 	= new THREE.Mesh(this.gradientPlane, this.greyGradientMaterial);
	this.greyGradient.position.x = 50;
	
	this.colorGradient 	= new THREE.Mesh( this.gradientPlane, this.colorGradientMaterial);
	this.colorGradient.position.x = 105;
	
	this.greyImage 		= new THREE.Mesh( this.imagePlane, this.greyImageMaterial);
	this.greyImage.position.x = -180;
	this.greyImage.position.y = +130;
	
	this.colorImage 	= new THREE.Mesh( this.imagePlane, this.colorImageMaterial);
	this.colorImage.position.x = -180;
	this.colorImage.position.y = -130;
	
	this.colorWheelMaterial = new THREE.ShaderMaterial(CODECO.ColorWheelShader(256.0, 200.0));
	
	this.colorWheelGeometry = new THREE.CircleGeometry(100, 50);
	this.colorWheel = new THREE.Mesh(this.colorWheelGeometry,  this.colorWheelMaterial);
	this.colorWheel.position.x = 250;
	this.colorWheel.position.y = -150;
	
	this.colorSphere = new THREE.CircleGeometry(5, 50);
	this.outlineMaterial = new THREE.MeshBasicMaterial({color: new THREE.Color("#000000") });
	
	this.fromMaterial = new THREE.MeshBasicMaterial({color: this.colorBegin });
	this.toMaterial = new THREE.MeshBasicMaterial({color: this.colorEnd });
	
	this.fromMesh = new THREE.Mesh(this.colorSphere, this.fromMaterial);
	this.toMesh = new THREE.Mesh(this.colorSphere, this.toMaterial);
	this.fromMesh.position.z = this.toMesh.position.z = 1;
	
	this.fromOutline = new THREE.Mesh(this.colorSphere, this.outlineMaterial);
	this.fromOutline.position.z = 0.9;
	this.fromOutline.scale.x = this.fromOutline.scale.y = 1.33;
	
	this.toOutline = new THREE.Mesh(this.colorSphere, this.outlineMaterial);
	this.toOutline.position.z = 0.9;
	this.toOutline.scale.x = this.toOutline.scale.y = 1.33;
	
	this.colorWheel.add(this.fromMesh);
	this.colorWheel.add(this.fromOutline);
	this.colorWheel.add(this.toMesh);
	this.colorWheel.add(this.toOutline);
	
	this.updateWheelPoint(this.fromMesh, this.fromOutline);
	this.updateWheelPoint(this.toMesh, this.toOutline);
	
	this.scene.add(this.greyGradient );
	this.scene.add(this.colorGradient);
	this.scene.add(this.greyImage );
	this.scene.add(this.colorImage );
	this.scene.add(this.colorWheel);
	
	var labRef = this;
	
	this.guiData = {
		from : conf.initFrom,
		to : conf.initTo,
		colors : [],
		pushColor : function (){labRef.pushColor();},
		popColor : function (){labRef.popColor();}
	};
	
	this.isResizing = false;
	this.rendererRect = this.renderer.domElement.getBoundingClientRect();
	if(conf.gui){
		this.gui = new dat.GUI({ autoPlace: false });
		document.getElementById(canvasContainerId).appendChild(this.gui.domElement);
		this.gui.domElement.style.position = "absolute";
		
		var handler = this.gui.addColor( this.guiData, 'from');
		handler.onChange(CODECO.AppState.changer);
		handler = this.gui.addColor( this.guiData, 'to');
		handler.onChange(CODECO.AppState.changer);	 
		
		if(conf.addColors){
			handler = this.gui.add( this.guiData, 'pushColor');
			handler.name("add color");	
			handler = this.gui.add( this.guiData, 'popColor');
			handler.name("remove last color");
		}
		
		
		this.updateGUIPosition();
	}
	
	
	
	
};

CODECO.GradientLab.prototype = {
		
		constructor : CODECO.GradientLab,
	    
	    updateGUIPosition : function() {
	    	var guiRect = this.gui.domElement.getBoundingClientRect();
	    	this.gui.domElement.style.top = this.rendererRect.top + "px";
	    	this.gui.domElement.style.left = (this.rendererRect.left+this.rendererRect.width-guiRect.width) + "px";
	    },
	    
	    updateWheelPoint : function (wheelPoint, outline) {
	    	var hsl = wheelPoint.material.color.getHSL();
	    	wheelPoint.position.x = Math.cos((hsl.h * 2.0 + 0.5) * Math.PI) * 200 * 0.5 * hsl.s;
	    	wheelPoint.position.y = -Math.sin((hsl.h * 2.0 + 0.5) * Math.PI) * 200 * 0.5 * hsl.s;
	    	outline.position.x = wheelPoint.position.x;
	    	outline.position.y = wheelPoint.position.y;
	    },
	    
		update : function() {	
			if(CODECO.AppState.changed){
				
				CODECO.AppState.changed = false;
				
				this.colorBegin.set(this.guiData.from);
				this.fromMaterial.color.set(this.guiData.from);
				this.colorEnd.set(this.guiData.to);
				this.toMaterial.color.set(this.guiData.to);
				
				this.colorGradientMaterial.uniforms.from.needsUpdate = true;
				this.colorGradientMaterial.uniforms.to.needsUpdate = true;
				
				this.colorImageMaterial.uniforms.from.needsUpdate = true;
				this.colorImageMaterial.uniforms.to.needsUpdate = true;
				
				this.updateWheelPoint(this.fromMesh, this.fromOutline);
				this.updateWheelPoint(this.toMesh, this.toOutline);
				
				for(var i = 0; i < this.guiData.colors.length; ++i){
					this.addedColors[i].mesh.material.color.set(this.guiData.colors[i].value);
					this.addedColors[i].position = this.guiData.colors[i].position;
					this.addedColors[i].updateMeshPosition();
					
					this.colorImageMaterial.uniforms.colors.value[i].set(this.addedColors[i].mesh.material.color.r, 
																			this.addedColors[i].mesh.material.color.g,
																			this.addedColors[i].mesh.material.color.b);
					this.colorImageMaterial.uniforms.colors.needsUpdate = true;
					
					this.colorImageMaterial.uniforms.positions.value[i] = this.addedColors[i].position;
					this.colorImageMaterial.uniforms.positions.needsUpdate = true;
					
					this.colorGradientMaterial.uniforms.colors.value[i].set(this.addedColors[i].mesh.material.color.r, 
							this.addedColors[i].mesh.material.color.g,
							this.addedColors[i].mesh.material.color.b);
					this.colorGradientMaterial.uniforms.colors.needsUpdate = true;
					
					this.colorGradientMaterial.uniforms.positions.value[i] = this.addedColors[i].position;
					this.colorGradientMaterial.uniforms.positions.needsUpdate = true;

				}
				
			}
			
			var cornerX = this.rendererRect.left + this.rendererRect.width;
			var cornerY = this.rendererRect.top + this.rendererRect.height;
			
			if((cornerX - 10 <= CODECO.AppState.mousePos.x && CODECO.AppState.mousePos.x <= cornerX + 5 &&
					cornerY - 10 <= CODECO.AppState.mousePos.y && CODECO.AppState.mousePos.y <= cornerY + 5) || this.isResizing)
				this.renderer.domElement.style.cursor = "nw-resize";
			else
				this.renderer.domElement.style.cursor = "default";
			
			if(CODECO.AppState.mousePressed) {
				if(!this.isResizing){
					if(cornerX - 10 <= CODECO.AppState.mousePos.x && CODECO.AppState.mousePos.x <= cornerX + 5 &&
							cornerY - 10 <= CODECO.AppState.mousePos.y && CODECO.AppState.mousePos.y <= cornerY + 5)
						this.isResizing = true;
				}
				if(this.isResizing) {
					this.rendererRect = this.renderer.domElement.getBoundingClientRect();
					var width = CODECO.AppState.mousePos.x - this.rendererRect.left + 5;
					var height = CODECO.AppState.mousePos.y - this.rendererRect.top + 5;
					this.renderer.setSize( width, height );
					this.camera.aspect	= width / height;
					this.camera.updateProjectionMatrix();
					this.render();
					if(this.gui !== undefined && this.gui !== null)
						this.updateGUIPosition();
				}
			}
			
			if(CODECO.AppState.mouseReleased) {
				CODECO.AppState.mouseReleased = false;
				CODECO.AppState.mousePressed = false;
				if(this.isResizing) {
					this.isResizing = false;
				}
			}
		},
		
		render : function() {
			this.renderer.render(this.scene, this.camera);
		},
		
		pushColor : function() {
			
			if(this.addedColors.length == 5)
				return;
			
			
			var initColor = "#000000", initPosition = 0.5;
			var material = new THREE.MeshBasicMaterial({color: new THREE.Color(initColor) });
			var mesh = new THREE.Mesh(this.colorSphere, material);
			mesh.position.z = 1;
			var outline = new THREE.Mesh(this.colorSphere, this.outlineMaterial);
			outline.position.z = 0.9;
			outline.scale.x = outline.scale.y = 1.33;
			this.colorWheel.add(mesh);
			this.colorWheel.add(outline);
			
			this.guiData.colors.push({value : initColor, position : initPosition});
			var handler = {color : null, position : null};
			handler.color = this.gui.addColor(this.guiData.colors[this.guiData.colors.length-1], 'value');
			handler.color.onChange(CODECO.AppState.changer);
			handler.color.name("color " + (this.guiData.colors.length-1));
			handler.position = this.gui.add(this.guiData.colors[this.guiData.colors.length-1], 'position', 0.0, 1.0);
			handler.position.onChange(CODECO.AppState.changer);
			handler.position.name("position " + (this.guiData.colors.length-1));
			
			var element = new CODECO.GradientElement(mesh, initPosition, handler, outline);
			element.updateMeshPosition();
			this.addedColors.push(element);
			
			var i = this.addedColors.length - 1;
			
			this.colorImageMaterial.uniforms.colors.value[i].set(this.addedColors[i].mesh.material.color.r, 
																this.addedColors[i].mesh.material.color.g,
																this.addedColors[i].mesh.material.color.b);
			this.colorImageMaterial.uniforms.colors.needsUpdate = true;
			
			this.colorImageMaterial.uniforms.positions.value[i] = this.addedColors[i].position;
			this.colorImageMaterial.uniforms.positions.needsUpdate = true;
			
			this.colorGradientMaterial.uniforms.colors.value[i].set(this.addedColors[i].mesh.material.color.r, 
			this.addedColors[i].mesh.material.color.g,
			this.addedColors[i].mesh.material.color.b);
			this.colorGradientMaterial.uniforms.colors.needsUpdate = true;
			
			this.colorGradientMaterial.uniforms.positions.value[i] = this.addedColors[i].position;
			this.colorGradientMaterial.uniforms.positions.needsUpdate = true;
			
			this.colorImageMaterial.uniforms.usedSteps.value = this.addedColors.length;
			this.colorImageMaterial.uniforms.usedSteps.needsUpdate = true;
			
			this.colorGradientMaterial.uniforms.usedSteps.value = this.addedColors.length;
			this.colorGradientMaterial.uniforms.usedSteps.needsUpdate = true;
			
		},
		
		popColor : function() {
			
			if(this.addedColors.length == 0)
				return;
			
			this.colorImageMaterial.uniforms.positions.value[this.addedColors.length - 1] = -1.0;
			this.colorImageMaterial.uniforms.positions.needsUpdate = true;
			this.colorGradientMaterial.uniforms.positions.value[this.addedColors.length - 1] = -1.0;
			this.colorGradientMaterial.uniforms.positions.needsUpdate = true;
			
			var element = this.addedColors.pop();
			this.guiData.colors.pop();
			
			this.colorWheel.remove(element.mesh);
			
			element.handler.color.remove();
			element.handler.position.remove();
			element.dispose();
			
			this.colorImageMaterial.uniforms.usedSteps.value = this.addedColors.length;
			this.colorImageMaterial.uniforms.usedSteps.needsUpdate = true;
			
			this.colorGradientMaterial.uniforms.usedSteps.value = this.addedColors.length;
			this.colorGradientMaterial.uniforms.usedSteps.needsUpdate = true;
			
		}
		
};
	